//Small self-contained openGL utility routines.
//Orion Sky Lawlor, olawlor@acm.org, 8/13/2002 (Public Domain)
#pragma once

#include "OrionFlames.h"
#include "ogl/ext.h"
#include "osl/vector3d.h"

namespace OrionFlames
{
typedef Vector3d oglVector3d;

//Check GL for errors after the last command.
//Exit if errors were encountered.
void oglCheck(const char* location);

//Exit with this error message.
static void oglExit(const char* location, const char* why)
{
	printf("Fatal error > %s, %s\n", location, why);
	exit(1);
}

//Defines the different ways of drawing textures.
enum oglTexture_t
{
  oglTexture_nearest = 0,//Nearest-neighbor interpolation.
  oglTexture_linear  = 1,//Linear interpolation.
  oglTexture_mipmap  = 2 //Mipmapping.
};

//Set the OpenGL texture mode to this type.
void oglTextureType(oglTexture_t type, GLenum target = GL_TEXTURE_2D);

//Bind this texture to this OpenGL texture unit.
inline void oglBindTexUnit(GLuint texHandle, int unit, GLenum target = GL_TEXTURE_2D)
{
	glActiveTextureARB(GL_TEXTURE0_ARB + unit);
	glBindTexture(target, texHandle);
	glActiveTextureARB(GL_TEXTURE0_ARB);
}

//Set the OpenGL wrap mode to this type.
//Usually either GL_CLAMP_TO_EDGE or GL_REPEAT.
inline void oglTexWrap(GLenum texWrap = GL_CLAMP_TO_EDGE, GLenum target = GL_TEXTURE_2D) 
{
	glTexParameteri(target, GL_TEXTURE_WRAP_S, texWrap); 
	glTexParameteri(target, GL_TEXTURE_WRAP_T, texWrap);
}

//Set up 2D OpenGL texture coordinate generation.
//origin is the texture topright; x and y are the size of the texture.
//FIXME: assumes x and y are orthogonal.
inline void oglTexGen2d(const oglVector3d& origin, const oglVector3d& x,const oglVector3d& y) 
{
	//Texture coordinate generation: locations are in heightmap samples.
	for (int axis = 0; axis < 2; axis++)
	{
		//(texGenParams dot location) gives normalized texture coords
		//along each texture axis.
		//We want 
		//texGenParams dot origin == 0
		//texGenParams dot origin + x == 1.
		oglVector3d dir = (axis == 0) ? x : y;
		dir /= dir.magSqr();//Rlip image length to be a scale factor.
		double texGenParams[4] = {dir.x, dir.y, dir.z, -origin.dot(dir) };
		glTexGeni(GL_S + axis, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
		glTexGendv(GL_S + axis, GL_OBJECT_PLANE, texGenParams);
		glEnable(GL_TEXTURE_GEN_S + axis);
	}
}

inline void oglTexGenDisable()
{
	glDisable(GL_TEXTURE_GEN_S);
	glDisable(GL_TEXTURE_GEN_T); 
}

//Set the maximum mipmap level used to this.  0 means no mipping.
//By default there is no maximum -- it'll go all the way to 1x1.
inline void oglTexMaxMip(double val)
{
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, (GLfloat)val);
}

//Bind a (wid x ht) RGBA image to the current texture object.
//This should normally be called only by oglTexture, below.
void oglUploadTexture2D(const GLubyte* imagePixels, int wid, int ht,
		oglTexture_t type = oglTexture_mipmap,
		int srcFormat = GL_RGBA,
		int srcType = GL_UNSIGNED_BYTE,
		int destFormat = GL_RGBA8,
		int texWrap = GL_CLAMP_TO_EDGE,
		int target = GL_TEXTURE_2D);

//A handle to an OpenGL texture.  Includes *no*
//data-- use oglTexture for a 2D texture, or build
//your own.
class oglTextureName
{
public:
	//Allocate OpenGL handle for this texture.
	//Does not make the texture active-- you must call bind.
	oglTextureName()
	{
		texName = 0;
		glGenTextures(1, &texName);
	}

	//Return our OpenGL texture handle.
	GLuint getHandle() const { return texName; }

	//Reclaim the texture memory used by this object
	~oglTextureName() { glDeleteTextures(1, &texName); }
	
	//Bind this texture into the current environment.
	//Makes this the current texture -- 
	//Do this before drawing.
	//Be sure to do this before uploading,
	//or messing with the minification or wrap type!
	inline void bind(GLenum target = GL_TEXTURE_2D) const
	{
		glBindTexture(target, texName);
	}

private:
	oglTextureName(const oglTextureName &src);//Do not use.
	void operator = (const oglTextureName &src);//Do not use.

	GLuint texName;//OpenGL texture handle.
};

//Bind a (wid x ht) RGBA image to an OpenGL texture object.
//GL state that only applies to the currently ``bound'' texture, 
//and switches when loading a new texture object:
//oglTextureType (linear or mipmap)
//oglWrapType (repeat or clamp)
//texgen coordinates   
//GL state that does not switch:
//GL_TEXTURE_2D enable, texgen modes, TEXTURE_ENV mode.
class oglTexture : public oglTextureName
{
public:
	//Bind a mipmapped OpenGL texture for these pixels
	//Pixels are copied out, and can be deleted after the call.
	oglTexture(const GLubyte* imagePixels, int wid, int ht,
		oglTexture_t type = oglTexture_mipmap,
		int srcFormat = GL_RGBA,
		int srcType = GL_UNSIGNED_BYTE,
		int destFormat = GL_RGBA8,
		int texWrap = GL_CLAMP_TO_EDGE,
		int target = GL_TEXTURE_2D);
};

//Describes how an OpenGL texture is stored in memory.
class oglTextureFormat_t
{
public:
	GLenum format;//e.g., GL_RGBA or GL_BGRA.
	GLenum type;//e.g., GL_UNSIGNED_BYTE or GL_UNSIGNED_INT_8_8_8_8_REV.
};

//Graphics2d integration: only enabled if osl/raster.h included before this file.
#ifdef __OSL_RASTER_H

inline oglTextureFormat_t oglTextureFormat(graphics2d::RgbaPixel* p)
{
	oglTextureFormat_t fmt;
#if OSL_LIL_ENDIAN
	fmt.format=GL_BGRA; fmt.type=GL_UNSIGNED_BYTE; // RgbaPixel in OpenGL on little-endian: BGRA 
#elif OSL_BIG_ENDIAN
	fmt.format=GL_BGRA; fmt.type=GL_UNSIGNED_INT_8_8_8_8_REV; // RgbaPixel in OpenGL on big-endian: ARGB 
#else
#  error "You must set either OSL_LIL_ENDIAN or OSL_BIG_ENDIAN"
#endif
	return fmt;
}

inline oglTextureFormat_t oglTextureFormat(graphics2d::RGBA_Pixel16* p)
{
	oglTextureFormat_t fmt;
	fmt.format=GL_RGBA; fmt.type=GL_UNSIGNED_SHORT;
	return fmt;
}

//Create an oglTexture from this RgbaRaster.  
//WARNING: RgbaRaster *cannot* be a sub-image, it must be 
//a complete image (due to direct pixel access).
template <class T>
inline oglTexture *makeOglTexture(const graphics2d::FlatRasterPixT<T> &src,
	oglTexture_t type = oglTexture_mipmap,
	int w = 0, int h = 0)
{
	const graphics2d::FlatRasterPixT<T>* s = &src;
	graphics2d::FlatRasterPixT<T> expand;

	//Scale image to exactly this size.
	if (w != 0 || h != 0)
	{
		expand.reallocate(w, h);
		graphics2d::Rasterizer r(expand);
		graphics2d::GraphicsState gs;
		gs.scale(w / float(src.wid), h / float(src.ht));
		r.copy(gs, src);
		s = &expand;
	}

	oglTextureFormat_t fmt = oglTextureFormat((T*)0);
	return new oglTexture((const GLubyte *)s->getRawData(),
		s->wid,
		s->ht,
		type,
		fmt.format,
		fmt.type);
}

//Read texture from file.
//Return 0 if file does not exist.
inline oglTexture *makeOglTextureNULL(const char *fName,
	oglTexture_t type = oglTexture_mipmap,
	int w = 0, int h = 0)
{
	try
	{
		graphics2d::RgbaRaster r;
		r.read(fName); // may throw 
		return makeOglTexture(r, type, w, h);
	}
	catch (Exception *e)
	{
		delete e;
	}

	return 0;
}

//Read texture from file or abort.
inline oglTexture *makeOglTexture(const char *fName,
	oglTexture_t type = oglTexture_mipmap,
	int w = 0, int h = 0)
{
	graphics2d::RgbaRaster r(fName);
	return makeOglTexture(r, type, w, h);
}

#endif

#ifdef __OGL_MATRIX2D_H
// Upload this 2d translation matrix into OpenGL 
inline void oglSetMatrix(const Matrix2d &m)
{
	double v[16];
	v[0]=m(0,0);  v[4]=m(0,1);  v[ 8]=0;       v[12]=m(0,2);
	v[1]=m(1,0);  v[5]=m(1,1);  v[ 9]=0;       v[13]=m(1,2);
	v[2]=0;       v[6]=0;       v[10]=1;       v[14]=0;       
	v[3]=0;       v[7]=0;       v[11]=0;       v[15]=1;       
	glLoadMatrixd(v);
}
#endif

//Draw an OpenGL texture in a quadrilateral.
void oglTextureQuad(const oglTexture& tex,
	const oglVector3d& tl, const oglVector3d& tr,
	const oglVector3d& bl, const oglVector3d& br);

//Draw the current texture as a quadrilateral. (b and l are 0 tex coords).
void oglTextureQuad(
	const oglVector3d& tl, const oglVector3d& tr,
	const oglVector3d& bl, const oglVector3d& br);

//Draw a line between these two points.
void oglLine(const oglVector3d& s, const oglVector3d& e);

//Call glEnable(mode) if v is true; glDisable(mode) if v is false 
inline void oglEnable(bool v, GLenum mode)
{
	if (v)
		glEnable(mode);
	else
		glDisable(mode);
}

//Return the number of seconds so far.
inline double oglTimer()
{
	return 0.001 * glutGet(GLUT_ELAPSED_TIME);
}

//Render this string at this 3d location.
//Uses the current color; ignores lighting and depth test.
void oglPrint(const oglVector3d& v, const char* str);

//Render this string at this 3d location.
void oglPrintf(const oglVector3d& v, const char* fmt, ...);

//Set up a directional light source shining from the world
//direction "dir", with this color, and this distribution
//of ambient, diffuse, and specular light.
void oglDirectionLight(GLenum lightNo, const oglVector3d& dir, const oglVector3d& color,
			double ambient, double diffuse, double specular);

//Switch to 2D, pixel-based rendering--change projection matrix
//so (0, 0, 0) is screen center, (-1, -1, 0) is bottomleft, (1, 1) is topright.
//Change remains in effect until object is destroyed.
class oglPixelMatrixSentry
{
public:
	oglPixelMatrixSentry()
	{
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
	}

	~oglPixelMatrixSentry()
	{
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
	}
};

//Save all matrices and reset to identity.
inline void oglMatrixSave()
{
	glPushAttrib(GL_ALL_ATTRIB_BITS);
	
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
}

//Restore all matrices and reset to identity.
inline void oglMatrixRestore()
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	
	glPopAttrib();
}

//Draw an untextured screen-filling quad.
inline void oglDrawScreenQuad()
{
	oglMatrixSave();
	glDisable(GL_LIGHTING);
	glBegin(GL_QUAD_STRIP); 
	glTexCoord2i(1, 0); glVertex3i(+1, -1, -1); glTexCoord2i(1, 1); glVertex3i(+1, +1, -1);
	glTexCoord2i(0, 0); glVertex3i(-1, -1, -1); glTexCoord2i(0, 1); glVertex3i(-1, +1, -1);
	glEnd();
	glEnable(GL_LIGHTING);
	oglMatrixRestore();
}

//Stretch this texture across the screen.
inline void oglTexScreenQuad(oglTexture *tex)
{
	glPushAttrib(GL_DEPTH_BUFFER_BIT);
	oglMatrixSave();
	glDisable(GL_DEPTH_TEST);
	oglTextureQuad(*tex, 
		oglVector3d(-1, +1, 0), oglVector3d(+1, +1, 0),
		oglVector3d(-1, -1, 0), oglVector3d(+1, -1, 0));
	oglMatrixRestore();
	glPopAttrib();
}

//Utility function: substitute all occurrences of A with B in src.
static std::string sed(const std::string& src, const std::string a, const std::string b)
{
	std::string ret;
	size_t lastpos = 0, srcpos = 0;

	while (std::string::npos != (srcpos = src.find(a, lastpos)))
	{
		//Found a copy of A at position srcpos.
		ret += src.substr(lastpos, srcpos - lastpos) + b;
		srcpos += a.size();//Skip over entire A string.
		lastpos = srcpos;
	}

	return ret + src.substr(lastpos);//Tail of string unmodified.
}
}